﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using NeturalMath.Properties;

namespace NeturalMath
{
    /// <summary>
    /// Represents a Complex number
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "i")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "I")]
    public struct Complex
    {
        #region Constructors

        /// <summary>
        /// Creates a new instance of the complex type
        /// </summary>
        /// <param name="real"></param>
        /// <param name="i"></param>
        
        public Complex(double real,double i)
            :this()
        {
            Real = real;
            I = i;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Represents the real component of a complex number
        /// </summary>
        public double Real { get; set; }

        /// <summary>
        /// Represents the imaginary component of a complex number
        /// </summary>
        
        public double I { get; set; }

        #endregion

        #region Method Overloads

        /// <summary>
        /// Compares to another object
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            var other = (Complex) obj;
            return other.Real == this.Real && other.I == this.I;
        }

        /// <summary>
        /// Gets a unique hash code for this value
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return Real.GetHashCode() ^ I.GetHashCode();
        }

        /// <summary>
        /// Gets a string representation of the value.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture,"({0},{1}i)",Real,I);
        }

        #endregion

        #region Operators

        /// <summary>
        /// Compares two complex number to determine equality
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        public static bool operator ==(Complex c1,Complex c2)
        {
            return (c1.Real == c2.Real) && (c1.I == c2.I);
        }

        /// <summary>
        /// Compares two complex number to determine if they are different.
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        public static bool operator !=(Complex c1, Complex c2)
        {
            return (c1.Real != c2.Real) || (c1.I != c2.I);
        }

        /// <summary>
        /// Turns a complex number into its own negative
        /// </summary>
        /// <param name="c1"></param>
        /// <returns></returns>
        public static Complex operator -(Complex c1)
        {
            return new Complex(-c1.Real, -c1.I);
        }

        /// <summary>
        /// Adds two complex values together and returns the result
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        public static Complex operator +(Complex c1,Complex c2)
        {
            return new Complex(c1.Real + c2.Real, c1.I + c2.I);
        }

        /// <summary>
        /// Adds two complex values together and returns the result
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        public static Complex operator -(Complex c1, Complex c2)
        {
            return new Complex(c1.Real - c2.Real, c1.I - c2.I);
        }

        /// <summary>
        /// Multiplies two complex numbers together and returns the result.
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        public static Complex operator *(Complex c1, Complex c2)
        {
            return new Complex(c1.Real*c2.Real - c1.I*c2.I, c1.Real*c2.I + c1.I*c2.Real);
        }

        /// <summary>
        /// Divides two complex numbers together and returns the result
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        public static Complex operator /(Complex c1, Complex c2)
        {
            if (c2.Real == 0.0 && c2.I == 0.0)
                throw new MathException(ErrorCodes.DivideByZero, Resources.DivideByZero);

            var newReal = (c1.Real * c2.Real + c1.I * c2.I) / (c2.Real * c2.Real + c2.I * c2.I);
            var newI= (c2.Real * c1.I - c1.Real * c2.I) / (c2.Real * c2.Real + c2.I * c2.I);

            return (new Complex(newReal, newI));
        }

        /// <summary>
        /// Performs a modulus division agains the complex number
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        public static Complex operator %(Complex c1,Complex c2)
        {
            var raw = c1 / c2;
            var abs = new Complex(Math.Truncate(raw.Real), Math.Truncate(raw.I));
            var product = c1 * abs;
            var diff = c1 - product;
            return diff;
        }

        public static Complex Pow(Complex c1,Complex c2)
        {
            var power = (int)c2.Real;

            if (power == c2.Real && power >= 0 && c2.I == .0)
            {
                var result = new Complex(1,0);
                if (power == 0) return result;
                var factor = c1;
                while (power != 0)
                {
                    if ((power & 1) != 0)
                    {
                        result = result * factor;
                    }
                    factor = factor * factor;
                    power >>= 1;
                }
                return result;
            }
            
            if ( c1.Real==0 && c1.I==0 )
            {
                return (c2.Real==0 && c2.I==0) 
                    ? new Complex(1,0) 
                    : new Complex(0,1);
            }

            var powers = c1.Real * c1.Real + c1.I * c1.I;
            var arg = Math.Atan2(c1.I, c1.Real);
            var mul = Math.Pow(powers, c2.Real / 2) * Math.Exp(-c2.I * arg);
            var common = c2.Real * arg + .5 * c2.I * Math.Log(powers);

            return new Complex(mul * Math.Cos(common), mul * Math.Sin(common));
        }

        /// <summary>
        /// Performs a modulus division agains the complex number
        /// </summary>
        /// <param name="c1"></param>
        /// <returns></returns>
        public static Complex operator ~(Complex c1)
        {
            return new Complex(c1.Real.Flip(), c1.I.Flip());
        }
        #endregion
    }
}
